package dpss.node.server;

import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.LinkedBlockingQueue;

import dpss.commons.CustomLogger;
import dpss.commons.ServerNode;
import dpss.commons.UDPMessage;

/**
 * A Server node that is initialized with data
 * @author musab.mirza
 *
 */
public class Server {
	
	private static CustomLogger logger = CustomLogger.getInstance();
	
	// Basic dependencies
	private DatagramSocket socket;
	private InetSocketAddress replicaManagerSocketAddress;

	// Server's properties
	private ServerNode serverNode;
	private HashMap<String, InetSocketAddress> otherLeaders;
	private HashSet<InetSocketAddress> myReplicas;
	private InetSocketAddress myLeader;
	private AccountDatabase accountDatabase;
	private Processor processor = null;
	
	// FIFO Queue and it's processor
	private LinkedBlockingQueue<UDPMessage> fifoQueue;
	private FifoQueueProcessor fifoQueueProcessor = null;
	
	
	
	
	
	/**
	 * When starting the server, we need the socket for communication and 
	 * the server initialization data
	 * 
	 * @param socket
	 * @param replicaManagerSocketAddress
	 */
	public Server(DatagramSocket socket, InetSocketAddress replicaManagerSocketAddress) {
		
		// Set data for ths server
		this.socket = socket;
		this.replicaManagerSocketAddress = replicaManagerSocketAddress;
		uninitialize();
	}


	/**
	 * Prints the server init message
	 */
	private void printInitMessage() {
		logger.log("\n\n"
				
				+ "******** SERVER INITIALIZED ********" + "\n"
				+ "Group:" + serverNode.getGroup() + "\n"
				+ "Role: " + (serverNode.isLeader()?"Leader":"Replica") + "\n"
				+ "My leader: " + (!serverNode.isLeader() ? myLeader:"none") + "\n"
				+ "Players info: " + accountDatabase.getPlayersInfo() + "\n"
				+ (serverNode.isLeader()?"Other leaders:" + otherLeaders.toString():"")
				+ "\n************************************\n");

	}


	/**
	 * Sets this server to uninitialized state
	 */
	public void uninitialize() {
		accountDatabase = new AccountDatabase();
		serverNode = null;
		otherLeaders = null;
		myReplicas = new HashSet<InetSocketAddress>();
		myLeader = null;
		fifoQueue = new LinkedBlockingQueue<UDPMessage>();
		processor = new Processor(accountDatabase);
		if (fifoQueueProcessor != null){
			fifoQueueProcessor.die();
		}
		fifoQueueProcessor = new FifoQueueProcessor(socket, processor, fifoQueue, myReplicas);
		fifoQueueProcessor.start();
	}
	
	/**
	 * Setter for myReplicas
	 * @param replicas
	 */
	public synchronized void setMyReplicas(HashSet<InetSocketAddress> replicas){
		myReplicas = replicas;
		fifoQueueProcessor.setMyReplicas(replicas);
	}


	/**
	 * Getter for myReplicas
	 * @return
	 */
	public HashSet<InetSocketAddress> getMyReplicas(){
		return myReplicas;
	}


	/**
	 * Checks if the the server is initialized or not
	 * @return
	 */
	public boolean isInitialized(){
		if (accountDatabase.isEmpty() && serverNode != null && otherLeaders != null)
			return true;
		return false;
	}


	/**
	 * 
	 * @param object 
	 * @param serverNode2
	 * @param otherLeaders2
	 */
	public void initialize(ServerNode serverNode,
			HashMap<String, InetSocketAddress> otherLeaders, InetSocketAddress myLeader) {
		accountDatabase = new AccountDatabase();
		this.serverNode = serverNode;
		this.otherLeaders = otherLeaders;
		this.myLeader = myLeader;
		printInitMessage();
	}
	
	
	/**
	 * Returns the Server node object of this server
	 * @return
	 */
	public ServerNode getServerNode(){
		return serverNode;
	}


	
	/**
	 * Tackles the UDP request. If this server is uninitialized, 
	 * it will ignore the request. If the server is a leader, it
	 * will add the request to the processing Queue, however
	 * if its a replica, it will process the request right away
	 * and return the result to the sender (which is the leader)
	 * 
	 * @param messageReceived
	 * @throws Exception 
	 */
	public synchronized void tackleDpssRequest(UDPMessage request) {
		
		// If we are the leader node
		if (serverNode.isLeader()) {
			
			// If the it was a response from replica, decrease the counter in fifoprocessor
			if (request.getMessage().equalsIgnoreCase("dpss_replica_response")){
				fifoQueueProcessor.decreaseReplicaWaitingCounter();
				
				// If this replica responded in failure, increase it's fail count
				if (!request.isSuccess()){
					logger.log("Informing replica manager about the failed replica: " + request.getSenderSocketAddress().toString());
					UDPMessage.sendMessage(socket, new UDPMessage("failNotify", request.getSenderSocketAddress(), true, ""), replicaManagerSocketAddress);
				}
			}
			
			// If it was a request from the front end then process accordingly
			else{
				logger.log("Received request: " + request.toString() + ", adding to processor queue");
				fifoQueueProcessor.queue(request);
			}
			
			

		// For the pupose of this demo, the replicas will only return TRUE or FALSE
		// based on the faulty flat
		} else {
			
			if (!serverNode.isFaulty()){
				logger.log("Simulating SUCCESS response to the leader");
				UDPMessage.sendMessage(socket, new UDPMessage("dpss_replica_response", null, true, ""), myLeader);
			} else {
				logger.log("Simulating FAILURE response to the leader");
				UDPMessage.sendMessage(socket, new UDPMessage("dpss_replica_response", null, false, "Faulty replica"), myLeader);
			}
		}
	}


	/**
	 * Receives all leader info and updates the instance variable otherLeaders
	 * @param allLeadersInfo
	 */
	public void updateOtherLeadersInfo(
			HashMap<String, InetSocketAddress> allLeadersInfo) {
		
		otherLeaders = allLeadersInfo;
		otherLeaders.remove( serverNode.getGroup() );
		logger.log("Updated other leaders info: " + otherLeaders.toString());
	}



	
	
	
}
